// 
// Copyright (c) 2006-2009 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

namespace Anculus.Core
{
	public sealed class ByteBuffer : IEquatable<ByteBuffer>
	{
		public ByteBuffer (int size)
		{
			if (size < 1)
				throw new ArgumentNullException ("size must be > 0");
			
			Raw = new byte[size];
		}
		
		public ByteBuffer (byte[] data)
		{
			if (data == null)
				throw new ArgumentNullException ("data");
			
			Raw = data;
		}
		
		public ByteBuffer (MemoryStream stream)
		{
			if (stream == null)
				throw new ArgumentNullException ("stream");
			
			Raw = stream.ToArray ();
		}
		
		public ByteBuffer (Stream stream)
		{
			if (stream == null)
				throw new ArgumentNullException ("stream");
			if (!stream.CanRead)
				throw new ArgumentException ("stream is not readable.");
			if (stream.Length > int.MaxValue)
				throw new InvalidOperationException ("stream is too big to load in memory.");
			
			int size = (int)stream.Length;
			Raw = new byte[size];

			int toread = size;
			while (toread > 0) {
				int read = stream.Read (Raw, size - toread, toread);
				if (read <= 0)
					break;
				toread -= read;
			}
		}
		
		public ByteBuffer (string data, StringFormat format)
		{
			if (data == null)
				throw new ArgumentNullException ("data");
			
			if (data.Length == 0) {
				Raw = new byte[0];
				return;
			}

			switch (format) {
			case StringFormat.Ascii:
				Raw = System.Text.Encoding.ASCII.GetBytes (data);
				break;
			case StringFormat.Utf7:
				Raw = System.Text.Encoding.UTF7.GetBytes (data);
				break;
			case StringFormat.Utf8:
				Raw = System.Text.Encoding.UTF8.GetBytes (data);
				break;
			case StringFormat.Utf16:
				Raw = System.Text.Encoding.Default.GetBytes (data);
				break;
			case StringFormat.Utf32:
				Raw = System.Text.Encoding.UTF32.GetBytes (data);
				break;
			case StringFormat.Hex:
				//TODO:
				break;
			case StringFormat.Base64:
				Raw = Convert.FromBase64String (data);
				break;
			default:
				throw new NotSupportedException ();
			}
		}
		
		public ByteBuffer (string data)
			: this (data, StringFormat.Base64)
		{
		}
		
		public byte[] Raw { get; private set; }
		
		public int Length
		{
			get { return Raw.Length; }
		}
		
		public override string ToString ()
		{
			return Convert.ToBase64String (Raw);
		}
		
		public string ToString (StringFormat format)
		{
			switch (format) {
			case StringFormat.Ascii:
				return System.Text.Encoding.ASCII.GetString (Raw);
			case StringFormat.Utf7:
				return System.Text.Encoding.UTF7.GetString (Raw);
			case StringFormat.Utf8:
				return System.Text.Encoding.UTF8.GetString (Raw);
			case StringFormat.Utf16:
				return System.Text.Encoding.Default.GetString (Raw);
			case StringFormat.Utf32:
				return System.Text.Encoding.UTF32.GetString (Raw);
			case StringFormat.Hex:
				//TODO:
				throw new NotImplementedException ();
				break;
			case StringFormat.Base64:
				return Convert.ToBase64String (Raw);
			default:
				throw new NotSupportedException ();
			}
		}
		
		public void SaveToFile (string fileName)
		{
			if (String.IsNullOrEmpty (fileName))
				throw new ArgumentException ("fileName");
			
			using (FileStream fs = File.Open (fileName, FileMode.Truncate, FileAccess.Write)) {
				using (BinaryWriter writer = new BinaryWriter (fs)) {
					writer.Write (Raw);
				}
			}
		}
		
		//TODO: SaveToIndexedFile, LoadFromIndexedFile ??
		
		public static ByteBuffer FromFile (string fileName) 
		{
			if (String.IsNullOrEmpty (fileName))
				throw new ArgumentException ("fileName");
			
			FileInfo fi = new FileInfo (fileName);
			if (fi.Length > int.MaxValue)
				throw new InvalidOperationException ("File is too big to load in memory.");

			using (FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.Read)) {
				return new ByteBuffer (fs);
			}
		}
		
		public static ByteBuffer FromBase64String (string data) 
		{
			if (String.IsNullOrEmpty (data))
				throw new ArgumentException ("data");
			
			return new ByteBuffer (Convert.FromBase64String (data));
		}
		
		public static ByteBuffer FromHexString (string data) 
		{
			if (String.IsNullOrEmpty (data))
				throw new ArgumentException ("data");

			int len = data.Length;
			if (len % 2 != 0)
				throw new ArgumentException ("A hexadecimal string must have an even length.");
			
			byte[] bytes = new byte[len / 2];
			for (int i = 0, pos = 0; i < len; i += 2, pos++) {
				bytes[pos] = Convert.ToByte (data.Substring (i, 2), 16);
			}
			return bytes;
		}
		
		public static implicit operator ByteBuffer (byte[] data) 
		{
			return new ByteBuffer (data);
		}
		
		public static implicit operator ByteBuffer (MemoryStream stream) 
		{
			return new ByteBuffer (stream);
		}
		
		public static implicit operator ByteBuffer (Stream stream) 
		{
			return new ByteBuffer (stream);
		}
		
		public static implicit operator ByteBuffer (string data) 
		{
			return new ByteBuffer (data);
		}
		
		public static implicit operator byte[] (ByteBuffer buffer) 
		{
			if (buffer == null)
				throw new ArgumentNullException ("buffer");
			
			return buffer.Raw;
		}
		
		public static implicit operator MemoryStream (ByteBuffer buffer) 
		{
			if (buffer == null)
				throw new ArgumentNullException ("buffer");
			
			return new MemoryStream (buffer.Raw);
		}

		public static implicit operator string (ByteBuffer buffer) 
		{
			if (buffer == null)
				throw new ArgumentNullException ("buffer");

			return Convert.ToBase64String (buffer.Raw);
		}

		/// <summary>
		/// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
		/// </summary>
		/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
		/// <returns>
		/// 	<c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
		/// </returns>
		public override bool Equals (object obj)
		{
			if (obj == null)
				return false;
			
			ByteBuffer other = (ByteBuffer)obj;
			return this == other;
		}

		public bool Equals (ByteBuffer other)
		{
			return this == other;
		}

		/// <summary>
		/// Returns a hash code for this instance.
		/// </summary>
		/// <returns>
		/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
		/// </returns>
		public override int GetHashCode ()
		{
			return Raw.GetHashCode ();
		}

		/// <summary>
		/// Implements the operator ==.
		/// </summary>
		/// <param name="b1">A ByteBuffer.</param>
		/// <param name="b2">A ByteBuffer.</param>
		/// <returns>The result of the comparison.</returns>
		public static bool operator == (ByteBuffer b1, ByteBuffer b2)
		{
			if (b1 == null && b2 == null)
				return true;
			else if (b1 == null || b2 == null)
				return false;
			
			int len = b1.Length;
			if (len != b2.Length)
				return false;

			for (int i = 0; i < len; i++) {
				if (b1.Raw[i] != b2.Raw[i])
					return false;
			}
			return true;
		}

		/// <summary>
		/// Implements the operator !=.
		/// </summary>
		/// <param name="b1">A ByteBuffer.</param>
		/// <param name="b2">A ByteBuffer.</param>
		/// <returns>The result of the comparison.</returns>
		public static bool operator != (ByteBuffer b1, ByteBuffer b2)
		{
			return !(b1 == b2);
		}
		
		internal string ToHexString ()
		{
			//partially copied from Mono System.BitConverter

			int len = Length;
			StringBuilder sb = new StringBuilder (len * 2);
			for (int i = 0; i < len; i++) {
				char high = (char)((Raw[i] >> 4) & 0x0F);
				char low = (char)(Raw[i] & 0x0F);
					
				if (high < 10) {
					high += '0';
				} else {
					high -= (char)10;
					high += 'A';
				}
					
				if (low < 10) {
					low += '0';
				} else {
					low -= (char)10;
					low += 'A';
				}
				
				sb.Append (high);
				sb.Append (low); 	
			}
			
			return sb.ToString ();
		}
	}
}